#include "ControladorAlmacenamientoBSharp.h"
#include "Excepciones.h"
#include "DatoBSharp.h"
#include "HandlerActualizacionPadre.h"
#include "Registro.h"

namespace Toolkit
{

ControladorAlmacenamientoBSharp::ControladorAlmacenamientoBSharp(
        AdministradorBufferingBSharp &administradorBuffering,
        const TipoSerializacion &tSerializacion,
        const unsigned long int &tamanoBloque, Almacenamiento &primario,
        WrapperComponente &wrapperPrimario, Almacenamiento &secundario,
        WrapperComponente &wrapperSecundario) :
    administradorBuffering(administradorBuffering)
{

    this->tamanoBloque = tamanoBloque;

    this->tSerializacion = tSerializacion;
    this->alturaBsharp = 0;

    //se crea la raiz
    BloqueBSharp raiz(tamanoBloque, tSerializacion);
    raiz.setDireccion(0);
    raiz.setNivelNodo(0);

    stringstream stream;
    wrapperPrimario.setComponente(&raiz);
    wrapperPrimario.serializar(stream, tSerializacion);
    primario.guardarDato(stream);

    stream.str("");
    wrapperSecundario.setComponente(&raiz);
    wrapperSecundario.serializar(stream, tSerializacion);
    secundario.guardarDato(stream);

    handlerPadre = new HandlerActualizacionPadre(this, tSerializacion);
    datoAuxiliar = NULL;

}

ControladorAlmacenamientoBSharp::ControladorAlmacenamientoBSharp(
        AdministradorBufferingBSharp &administradorBuffering,
        const TipoSerializacion &tSerializacion,
        const unsigned long int &tamanoBloque,
        const unsigned long int &alturaBSharp) :
    administradorBuffering(administradorBuffering)
{
    this->tamanoBloque = tamanoBloque;

    this->tSerializacion = tSerializacion;

    this->alturaBsharp = alturaBSharp;

    handlerPadre = new HandlerActualizacionPadre(this, tSerializacion);
    datoAuxiliar = NULL;
}

ControladorAlmacenamientoBSharp::~ControladorAlmacenamientoBSharp()
{
    delete handlerPadre;

    if (datoAuxiliar != NULL) {
        delete datoAuxiliar;
    }
}

void ControladorAlmacenamientoBSharp::guardarDato(
        Almacenamiento &almacenamiento, const Dato &dato, const Clave &clave,
        const unsigned long int posicion, WrapperComponente &wrapper)
{

    BloqueBSharp *bloque = new BloqueBSharp(tamanoBloque, tSerializacion);
    wrapper.setComponente(bloque);

    stringstream stream;
    almacenamiento.recuperarDato(posicion, stream);
    wrapper.hidratar(stream, dato, tSerializacion);

    agregarDato(almacenamiento, *bloque, dato, clave, posicion, wrapper);

    delete bloque;
}

void ControladorAlmacenamientoBSharp::eliminarDato(
        Almacenamiento &almacenamiento, const long unsigned int posicion,
        Dato &dato, const Clave &clave, WrapperComponente &wrapper)
{

    stringstream stream;
    almacenamiento.recuperarDato(posicion, stream);

    BloqueBSharp *bloque = new BloqueBSharp(tamanoBloque, tSerializacion);
    wrapper.setComponente(bloque);
    wrapper.hidratar(stream, dato, tSerializacion);

    long int posPadre = -1;
    long int posIzq = -1;
    long int posDer = -1;
    BloqueBSharp *padre = NULL;
    BloqueBSharp *siblingIzquierdo = NULL;
    BloqueBSharp *siblingDerecho = NULL;

    if ((!esRaiz(*bloque))) {
        posPadre = buscarPadre(bloque);
        padre = administradorBuffering.buscarBloque(posPadre);
        Clave *claveMaxima = bloque->claveMaxima();

        //obtengo siblings
        posIzq = padre->siblingIzquierdo(*claveMaxima);
        if (posIzq != -1) {
            siblingIzquierdo = administradorBuffering.buscarBloque(posIzq);
        }

        posDer = padre->siblingDerecho(*claveMaxima);
        if (posDer != -1) {
            siblingDerecho = administradorBuffering.buscarBloque(posDer);
        }

        //preparo el handler
        handlerPadre->setBloqueActual(bloque, posPadre, padre,
                siblingIzquierdo, siblingDerecho);
    }

    delete bloque->remover(clave);

    if (bloque->cantElementos() == 0) {
        if (!esRaiz(*bloque)) {
            almacenamiento.eliminarDato(posicion, stream);
            //la clave que elimino debía ser la más grande del bloque
            eliminarDato(almacenamiento, posPadre, dato, clave, wrapper);
        }
        else {
            stream.str("");
            wrapper.serializar(stream, tSerializacion);
            almacenamiento.guardarDato(stream, posicion);

            administradorBuffering.agregarComponenteAlterado(posicion);
        }

    }
    else if (bloque->estaUnderflow()) {
        manejarUnderflow(almacenamiento, *bloque, posicion, wrapper, posPadre,
                padre, posIzq, siblingIzquierdo, posDer, siblingDerecho);
    }
    else {
        stream.str("");
        wrapper.serializar(stream, tSerializacion);
        almacenamiento.guardarDato(stream, posicion);

        administradorBuffering.agregarComponenteAlterado(posicion);

        if (!esRaiz(*bloque)) {
            handlerPadre->actualizar(almacenamiento, bloque, siblingIzquierdo,
                    siblingDerecho, wrapper);
        }
    }

    if (padre != NULL) {
        delete padre;
    }
    if (siblingDerecho != NULL) {
        delete siblingDerecho;
    }
    if (siblingIzquierdo != NULL) {
        delete siblingIzquierdo;
    }
    delete bloque;
}

void ControladorAlmacenamientoBSharp::recuperarComponente(
        Almacenamiento &almacenamiento, const unsigned long int &posicion,
        WrapperComponente &wrapper)
{
    stringstream stream;
    almacenamiento.recuperarDato(posicion, stream);

    BloqueBSharp *bloque = new BloqueBSharp(tamanoBloque, tSerializacion);
    wrapper.setComponente(bloque);

    wrapper.hidratar(stream, (*this->datoAuxiliar), tSerializacion);

}

void ControladorAlmacenamientoBSharp::guardarComponente(
        Almacenamiento &almacenamiento, const unsigned long int &posicion,
        WrapperComponente &wrapper)
{

    stringstream stream;
    wrapper.serializar(stream, tSerializacion);

    almacenamiento.guardarDato(stream, posicion);

}

unsigned long int ControladorAlmacenamientoBSharp::guardarComponente(
        Almacenamiento &almacenamiento, WrapperComponente &wrapper)
{

    stringstream stream;
    wrapper.serializar(stream, tSerializacion);

    return almacenamiento.guardarDato(stream);
}

bool ControladorAlmacenamientoBSharp::esRaiz(const BloqueBSharp &bloque) const
{

    return (bloque.getNivelNodo() == this->alturaBsharp);
}

void ControladorAlmacenamientoBSharp::agregarDato(
        Almacenamiento &almacenamiento, BloqueBSharp &bloque, const Dato &dato,
        const Clave &clave, const unsigned long int &posicion,
        WrapperComponente &wrapper)
{

    if (!esRaiz(bloque)) {
        //obtengo el padre
        unsigned long int posPadre = buscarPadre(&bloque);
        BloqueBSharp *padre = administradorBuffering.buscarBloque(posPadre);
        Clave *claveMaxima = bloque.claveMaxima();

        //obtengo siblings
        long int posIzq = padre->siblingIzquierdo(*claveMaxima);
        BloqueBSharp *siblingIzquierdo = NULL;
        if (posIzq != -1) {
            siblingIzquierdo = administradorBuffering.buscarBloque(posIzq);
        }

        long int posDer = padre->siblingDerecho(*claveMaxima);
        BloqueBSharp *siblingDerecho = NULL;
        if (posDer != -1) {
            siblingDerecho = administradorBuffering.buscarBloque(posDer);
        }

        //preparo el handler
        handlerPadre->setBloqueActual(&bloque, posPadre, padre,
                siblingIzquierdo, siblingDerecho);

        //agrego el dato
        Registro* registro = new Registro(tSerializacion);
        registro->setDato(dato);
        bloque.agregar(registro, clave);

        if (bloque.estaOverflow()) {
            manejarOverflow(almacenamiento, bloque, posicion, wrapper,
                    siblingIzquierdo, posIzq, siblingDerecho, posDer);
        }
        else if (bloque.estaUnderflow()) {
            manejarUnderflow(almacenamiento, bloque, posicion, wrapper,
                    posPadre, padre, posIzq, siblingIzquierdo, posDer,
                    siblingDerecho);

        }
        else { //No hay overflow ni underflow

            guardarBloqueEnPosicion(bloque, almacenamiento, posicion, wrapper);
            handlerPadre->actualizar(almacenamiento, &bloque, siblingIzquierdo,
                    siblingDerecho, wrapper);
        }

        delete claveMaxima;
        delete padre;
        if (siblingDerecho != NULL) {
            delete siblingDerecho;
        }
        if (siblingIzquierdo != NULL) {
            delete siblingIzquierdo;
        }

    }
    else { //es raiz

        Registro* registro = new Registro(tSerializacion);
        registro->setDato(dato);
        bloque.agregar(registro, clave);
        if (bloque.estaOverflowRaiz()) {
            manejarOverflowRaiz(almacenamiento, bloque, posicion, wrapper);

        }
        else {
            guardarBloqueEnPosicion(bloque, almacenamiento, posicion, wrapper);

        }

    }
}

void ControladorAlmacenamientoBSharp::manejarUnderflow(
        Almacenamiento &almacenamiento, BloqueBSharp &bloque,
        const unsigned long int posicion, WrapperComponente &wrapper,
        const long int &posPadre, BloqueBSharp *padre, const long int &posIzq,
        BloqueBSharp *siblingIzq, const long int &posDer,
        BloqueBSharp *siblingDer)
{
    bool yaBalanceo = false;

    if (esRaiz(*padre) && (padre->cantElementos() == 2)) {
        if ((siblingIzq != NULL) && (siblingIzq->sePuedeUnirRaiz(bloque))) {

            this->alturaBsharp--;

            siblingIzq->unirRaiz(bloque);
            if (siblingIzq->esHoja()) {
                siblingIzq->setDireccion(bloque.getDireccion());
            }

            //el izq queda como raiz, el derecho se elimina
            guardarBloqueEnPosicion(*siblingIzq, almacenamiento, posPadre,
                    wrapper);

            stringstream stream;
            stream.str("");
            almacenamiento.eliminarDato(posicion, stream); //se elimina e bloque
            stream.str("");
            almacenamiento.eliminarDato(posIzq, stream); //se elimina el viejo sibling

            yaBalanceo = true;

        }
        else if ((siblingDer != NULL) && (bloque.sePuedeUnirRaiz(*siblingDer))) {

            this->alturaBsharp--;

            bloque.unirRaiz(*siblingDer);
            if (bloque.esHoja()) {
                bloque.setDireccion(siblingDer->getDireccion());
            }

            //el bloque queda como raiz, el derecho se elimina
            guardarBloqueEnPosicion(bloque, almacenamiento, posPadre, wrapper);

            stringstream stream;
            stream.str("");
            almacenamiento.eliminarDato(posicion, stream); //se elimina el bloque viejo
            stream.str("");
            almacenamiento.eliminarDato(posDer, stream); //se elimina el sibling

            yaBalanceo = true;

        }

    }
    else { //no es raíz
        if ((siblingIzq != NULL) && (siblingIzq->sePuedeBalancearEnDos(bloque))) {
            balanceoEntreDosIzq(almacenamiento, bloque, posicion, wrapper,
                    *siblingIzq, posIzq);
            yaBalanceo = true;

        }
        else if ((siblingDer != NULL) && (bloque.sePuedeBalancearEnDos(
                *siblingDer))) {
            balanceoEntreDosDer(almacenamiento, bloque, posicion, wrapper,
                    *siblingDer, posDer);
            yaBalanceo = true;

        }
        else if ((siblingDer != NULL) && (siblingIzq != NULL)
                && (siblingIzq->sePuedeUnirTresEnDos(bloque, *siblingDer))) {

            siblingIzq->unirTresEnDos(bloque, *siblingDer);

            //se elimina el bloque
            stringstream stream;
            almacenamiento.eliminarDato(posicion, stream);

            //se guarda el derecho
            guardarBloqueEnPosicion(*siblingDer, almacenamiento, posDer,
                    wrapper);

            //se guarda el izquierdo
            if (bloque.esHoja()) {
                siblingIzq->setDireccion(bloque.getDireccion());
            }

            guardarBloqueEnPosicion(*siblingIzq, almacenamiento, posIzq,
                    wrapper);

            handlerPadre->actualizarUnirTresEnDos(almacenamiento, siblingIzq,
                    wrapper);

            yaBalanceo = true;
        }
    }

    if (!yaBalanceo) { //No se pudo solucionar el underflow
        guardarBloqueEnPosicion(bloque, almacenamiento, posicion, wrapper);
        handlerPadre->actualizar(almacenamiento, &bloque, siblingIzq,
                siblingDer, wrapper);
    }

}

void ControladorAlmacenamientoBSharp::manejarOverflow(
        Almacenamiento &almacenamiento, BloqueBSharp &bloque,
        const unsigned long int posicion, WrapperComponente &wrapper,
        BloqueBSharp *siblingIzq, const long int & posIzq,
        BloqueBSharp *siblingDer, const long int &posDer)
{
    bool yaBalanceo = false;

    if (((posIzq != -1) && (siblingIzq->sePuedeBalancearEnDos(bloque)))) {
        balanceoEntreDosIzq(almacenamiento, bloque, posicion, wrapper,
                *siblingIzq, posIzq);

        yaBalanceo = true;
    }

    if ((!yaBalanceo) && (posDer != -1) && (bloque.sePuedeBalancearEnDos(
            (*siblingDer)))) {
        balanceoEntreDosDer(almacenamiento, bloque, posicion, wrapper,
                *siblingDer, posDer);

        yaBalanceo = true;
    }

    //Balanceo entre 3
    if (!yaBalanceo) {
        //Intento balancear en 3 con el izquierdo
        BloqueBSharp bloqueNuevo(bloque.getTamanoBloque(), tSerializacion);
        bloqueNuevo.setNivelNodo(bloque.getNivelNodo());

        bool sePuedeIzq = ((siblingIzq != NULL)
                && (siblingIzq->sePuedeBalancearEnTres(bloqueNuevo, bloque)));
        bool sePuedeDer = ((siblingDer != NULL)
                && (bloque.sePuedeBalancearEnTres(bloqueNuevo, (*siblingDer))));

        if ((sePuedeIzq) || ((!sePuedeDer) && (siblingIzq != NULL))) { //Uso el izquierdo

            siblingIzq->balancearEnTres(bloqueNuevo, bloque);

            //guardo el bloque
            guardarBloqueEnPosicion(bloque, almacenamiento, posicion, wrapper);

            //guardo el central
            if (bloqueNuevo.esHoja()) {
                bloqueNuevo.setDireccion(siblingIzq->getDireccion());
            }

            unsigned long int posicionNuevo = guardarBloqueNuevo(bloqueNuevo,
                    almacenamiento, wrapper);

            //guardo el izquierdo
            if (siblingIzq->esHoja()) {
                siblingIzq->setDireccion(posicionNuevo);
            }

            guardarBloqueEnPosicion(*siblingIzq, almacenamiento, posIzq,
                    wrapper);

            handlerPadre->actualizarPartirDosEnTres(almacenamiento,
                    bloqueNuevo, posicionNuevo, &bloque, siblingIzq, NULL,
                    wrapper);

        }
        else if (siblingDer != NULL) {//uso el derecho

            bloque.balancearEnTres(bloqueNuevo, *siblingDer);

            //guardo el central
            if (bloqueNuevo.esHoja()) {
                bloqueNuevo.setDireccion(bloque.getDireccion()); //apunta al derecho
            }

            unsigned long int posicionNuevo = guardarBloqueNuevo(bloqueNuevo,
                    almacenamiento, wrapper);

            //guardo el bloque
            if (bloque.esHoja()) {
                bloque.setDireccion(posicionNuevo); //apunta al central
            }

            guardarBloqueEnPosicion(bloque, almacenamiento, posicion, wrapper);

            guardarBloqueEnPosicion(*siblingDer, almacenamiento, posDer,
                    wrapper);

            handlerPadre->actualizarPartirDosEnTres(almacenamiento,
                    bloqueNuevo, posicionNuevo, &bloque, NULL, siblingDer,
                    wrapper);

        }
        else { //no se pudo salvar el overflow, se crea un sibling nuevo a la derecha y se balancea entre 2

            BloqueBSharp *nuevoDerecho = bloque.partirRaiz(); //no muy apropiado pero sirve

            nuevoDerecho->setNivelNodo(bloque.getNivelNodo());

            if (nuevoDerecho->esHoja()) {
            	nuevoDerecho->setDireccion(bloque.getDireccion());
            }

            //guardo el bloque y su nuevo sibling derecho
            unsigned long int posDer = guardarBloqueNuevo(*nuevoDerecho,
                    almacenamiento, wrapper);

            if (bloque.esHoja()) {
            	bloque.setDireccion(posDer);
            }

            guardarBloqueEnPosicion(bloque, almacenamiento, posicion, wrapper);

            handlerPadre->actualizarPartirUnoEnDos(almacenamiento,
                    *nuevoDerecho, posDer, &bloque, wrapper);

            delete nuevoDerecho;

        }

    }

}

void ControladorAlmacenamientoBSharp::manejarOverflowRaiz(
        Almacenamiento &almacenamiento, BloqueBSharp &bloque,
        const unsigned long int posicionReal, WrapperComponente &wrapper)
{
    this->alturaBsharp++;

    //el bloque es ahora el sibling izquierdo
    BloqueBSharp *nuevoDerecho = bloque.partirRaiz();
    BloqueBSharp nuevaRaiz(bloque.getTamanoBloque(), tSerializacion);

    stringstream stream;
    wrapper.setComponente(&bloque);
    wrapper.serializar(stream, tSerializacion);

    unsigned long int posIzq = almacenamiento.guardarDato(stream);

    nuevoDerecho->setNivelNodo(bloque.getNivelNodo());
    nuevoDerecho->setDireccion(posIzq);

    unsigned long int posDer = guardarBloqueNuevo(*nuevoDerecho,
            almacenamiento, wrapper);

    //se vuelve a guardar izq con direccion siguiente correcta
    bloque.setDireccion(posDer);
    guardarBloqueEnPosicion(bloque, almacenamiento, posIzq, wrapper);

    Clave *claveMaxIzq = bloque.claveMaxima();
    Clave *claveMaxDer = nuevoDerecho->claveMaxima();

    nuevaRaiz.setNivelNodo(alturaBsharp);

    //subo la clave izquierda
    DatoBSharp datoIzq((*claveMaxIzq), posIzq, tSerializacion);
    Registro *registroIzq = new Registro(tSerializacion);
    registroIzq->setDato(datoIzq);
    nuevaRaiz.agregar(registroIzq, (*claveMaxIzq));

    //subo la clave derecha
    DatoBSharp datoDer((*claveMaxDer), posDer, tSerializacion);
    Registro *registroDer = new Registro(tSerializacion);
    registroDer->setDato(datoDer);
    nuevaRaiz.agregar(registroDer, (*claveMaxDer));

    guardarBloqueEnPosicion(nuevaRaiz, almacenamiento, posicionReal, wrapper);

    delete claveMaxIzq;
    delete claveMaxDer;
    delete nuevoDerecho;

}

void ControladorAlmacenamientoBSharp::balanceoEntreDosIzq(
        Almacenamiento &almacenamiento, BloqueBSharp &bloque,
        const unsigned long int posicion, WrapperComponente &wrapper,
        BloqueBSharp &siblingIzq, unsigned long int posIzq)
{
    siblingIzq.balancearEnDos(bloque);

    guardarBloqueEnPosicion(bloque, almacenamiento, posicion, wrapper);

    guardarBloqueEnPosicion(siblingIzq, almacenamiento, posIzq, wrapper);

    handlerPadre->actualizar(almacenamiento, &bloque, &siblingIzq, NULL,
            wrapper); //TODO asegurarse que ese null no trae problemas
}

void ControladorAlmacenamientoBSharp::balanceoEntreDosDer(
        Almacenamiento &almacenamiento, BloqueBSharp &bloque,
        const unsigned long int posicion, WrapperComponente &wrapper,
        BloqueBSharp &siblingDer, unsigned long int posDer)
{
    bloque.balancearEnDos(siblingDer);

    guardarBloqueEnPosicion(bloque, almacenamiento, posicion, wrapper);

    guardarBloqueEnPosicion(siblingDer, almacenamiento, posDer, wrapper);

    handlerPadre->actualizar(almacenamiento, &bloque, NULL, &siblingDer,
            wrapper);//TODO asegurarse que ese null no trae problemas
}

void ControladorAlmacenamientoBSharp::guardarBloqueEnPosicion(
        BloqueBSharp &bloque, Almacenamiento &almacenamiento,
        const unsigned long int &posicion, WrapperComponente &wrapper)
{

    stringstream stream;
    wrapper.setComponente(&bloque);
    wrapper.serializar(stream, tSerializacion);
    almacenamiento.guardarDato(stream, posicion);
    administradorBuffering.agregarComponenteAlterado(posicion);

}

unsigned long int ControladorAlmacenamientoBSharp::guardarBloqueNuevo(
        BloqueBSharp &bloque, Almacenamiento &almacenamiento,
        WrapperComponente &wrapper)
{
    stringstream stream;
    wrapper.setComponente(&bloque);
    wrapper.serializar(stream, tSerializacion);
    unsigned long int posicionNuevo = almacenamiento.guardarDato(stream);
    administradorBuffering.agregarComponenteAlterado(posicionNuevo);

    return posicionNuevo;
}

void ControladorAlmacenamientoBSharp::imprimirArbol(
        Almacenamiento &almacenamiento, const unsigned long int posicion) const
{
    stringstream ss;
    almacenamiento.recuperarDato(posicion, ss);
    BloqueBSharp bloque(tamanoBloque, tSerializacion);
    bloque.hidratar(ss, (*this->datoAuxiliar));

    for (unsigned long int i = 0; i < (alturaBsharp - bloque.getNivelNodo()); i++) {
        cout << "   ";
    }
    cout << "Pos: " << posicion << ", ";

    list<unsigned long int> componentes = bloque.imprimirYPoisciones();

    if (bloque.getNivelNodo() != 0) {
        unsigned long int cantComponentes = componentes.size();
        for (unsigned long int i = 0; i < cantComponentes; i++) {
            unsigned long int posicion2 = componentes.front();
            componentes.pop_front();
            this-> imprimirArbol(almacenamiento, posicion2);
        }
    }

}

unsigned long int ControladorAlmacenamientoBSharp::buscarPadre(
        const BloqueBSharp *bloque) const
{
    Clave *clave = bloque->claveMinima();

    unsigned long int posPadre = administradorBuffering.busquedaInexacta(
            *clave, bloque->getNivelNodo() + 1);

    delete clave;

    return posPadre;
}

unsigned long int ControladorAlmacenamientoBSharp::getAlturaBSharp() const
{
    return this->alturaBsharp;
}

void ControladorAlmacenamientoBSharp::setDatoAuxiliar(const DatoBSharp &dato)
{
    if (this->datoAuxiliar == NULL) {
        this->datoAuxiliar = dato.clonar();
    }
}

}
